package ast;

import java.io.PrintStream;
import java.util.Scanner;
import java.util.Map;
import java.util.Stack;

public class CompExpr extends CondExpr {
	public static final int GE = 1;
	public static final int GT = 2;
	public static final int LE = 3;
	public static final int LT = 4;
	public static final int EQ = 5;
	public static final int NE = 6;
	public final Expr expr1, expr2;
	public final int op;

	public CompExpr(Expr e1, int oper, Expr e2, Location loc) {
		super(loc);
		expr1 = e1;
		expr2 = e2;
		op = oper;
	}

	public void print(PrintStream ps) {
		ps.print("(");
		expr1.print(ps);
		switch (op) {
			case GE:
				ps.print(">=");
				break;
			case GT:
				ps.print(">");
				break;
			case LE:
				ps.print("<=");
				break;
			case LT:
				ps.print("<");
				break;
			case EQ:
				ps.print("==");
				break;
			case NE:
				ps.print("!=");
				break;
		}
		expr2.print(ps);
		ps.print(")");
	}

	// interpret: evaluate the comparison expression ( greater than, less than, equal, etc.), return 1 if true, 0 if false
	public AbstractBoolValue evaluate(Stack<Map<String, AbstractValue>> stateStack, Scanner scanner) throws Exception {
		AbstractValue n1 = expr1.evaluate(stateStack, scanner);
		AbstractValue n2 = expr2.evaluate(stateStack, scanner);
		switch (op) {
			// greater than or equal
			case GE:
				/**
				 * Neg >= Neg -> Anybool
				 * Neg >= Zero -> False
				 * Neg >= Pos -> False
				 * Neg >= Any -> Anybool
				 *
				 * Zero >= Neg -> True
				 * Zero >= Zero -> True
				 * Zero >= Pos -> False
				 * Zero >= Any -> Anybool
				 *
				 * Pos >= Neg -> True
				 * Pos >= Zero -> True
				 * Pos >= Pos -> Anybool
				 * Pos >= Any -> Anybool
				 *
				 * Any >= Neg -> Anybool
				 * Any >= Zero -> Anybool
				 * Any >= Pos -> Anybool
				 * Any >= Any -> Anybool
				 */
				switch (n1) {
					case NegInt:
						switch (n2) {
							case NegInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.False;
						}
					case ZeroInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.True;
							case PosInt:
								return AbstractBoolValue.False;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case PosInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.True;
							case PosInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case AnyInt:
						return AbstractBoolValue.AnyBool;

					case NegFloat:
						switch (n2) {
							case NegFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.False;
						}
					case ZeroFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.True;
							case PosFloat:
								return AbstractBoolValue.False;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case PosFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.True;
							case PosFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case AnyFloat:
						return AbstractBoolValue.AnyBool;

					default:
						throw new Exception("Invalid AbstractValue type");
				}
			// greater than
			case GT:
				/**
				 * Neg > Neg -> Anybool
				 * Neg > Zero -> False
				 * Neg > Pos -> False
				 * Neg > Any -> Anybool
				 *
				 * Zero > Neg -> True
				 * Zero > Zero -> False
				 * Zero > Pos -> False
				 * Zero > Any -> Anybool
				 *
				 * Pos > Neg -> True
				 * Pos > Zero -> True
				 * Pos > Pos -> Anybool
				 * Pos > Any -> Anybool
				 *
				 * Any > Neg -> Anybool
				 * Any > Zero -> Anybool
				 * Any > Pos -> Anybool
				 * Any > Any -> Anybool
				 */

				switch (n1) {
					case NegInt:
						switch (n2) {
							case NegInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.False;
						}
					case ZeroInt:
						switch (n2) {
							case NegInt:
								return AbstractBoolValue.True;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.False;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case PosInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.True;
							case PosInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case AnyInt:
						return AbstractBoolValue.AnyBool;

					case NegFloat:
						switch (n2) {
							case NegFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.False;
						}
					case ZeroFloat:
						switch (n2) {
							case NegFloat:
								return AbstractBoolValue.True;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.False;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case PosFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.True;
							case PosFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case AnyFloat:
						return AbstractBoolValue.AnyBool;

					default:
						throw new Exception("Invalid AbstractValue type");
				}
			// less than or equal
			case LE:
				/**
				 * Neg <= Neg -> Anybool
				 * Neg <= Zero -> True
				 * Neg <= Pos -> True
				 * Neg <= Any -> Anybool
				 *
				 * Zero <= Neg -> False
				 * Zero <= Zero -> True
				 * Zero <= Pos -> True
				 * Zero <= Any -> Anybool
				 *
				 * Pos <= Neg -> False
				 * Pos <= Zero -> False
				 * Pos <= Pos -> Anybool
				 * Pos <= Any -> Anybool
				 *
				 * Any <= Neg -> Anybool
				 * Any <= Zero -> Anybool
				 * Any <= Pos -> Anybool
				 * Any <= Any -> Anybool
				 */

				switch (n1) {
					case NegInt:
						switch (n2) {
							case NegInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.True;
						}
					case ZeroInt:
						switch (n2) {
							case NegInt:
								return AbstractBoolValue.False;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.True;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case PosInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.False;
							case PosInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case AnyInt:
						return AbstractBoolValue.AnyBool;

					case NegFloat:
						switch (n2) {
							case NegFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.True;
						}
					case ZeroFloat:
						switch (n2) {
							case NegFloat:
								return AbstractBoolValue.False;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.True;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case PosFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.False;
							case PosFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case AnyFloat:
						return AbstractBoolValue.AnyBool;

					default:
						throw new Exception("Invalid AbstractValue type");
				}

			// less than
			case LT:
				/**
				 * Neg < Neg -> Anybool
				 * Neg < Zero -> True
				 * Neg < Pos -> True
				 * Neg < Any -> Anybool
				 * 
				 * Zero < Neg -> False
				 * Zero < Zero -> False
				 * Zero < Pos -> True
				 * Zero < Any -> Anybool
				 * 
				 * Pos < Neg -> False
				 * Pos < Zero -> False
				 * Pos < Pos -> Anybool
				 * Pos < Any -> Anybool
				 * 
				 * Any < Neg -> Anybool
				 * Any < Zero -> Anybool
				 * Any < Pos -> Anybool
				 * Any < Any -> Anybool
				 */
				switch (n1) {
					case NegInt:
						switch (n2) {
							case NegInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.True;
						}
					case ZeroInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.False;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
							case PosInt:
								return AbstractBoolValue.True;
						}
					case PosInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.False;
							case PosInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case AnyInt:
						return AbstractBoolValue.AnyBool;

					case NegFloat:
						switch (n2) {
							case NegFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.True;
						}
					case ZeroFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.False;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
							case PosFloat:
								return AbstractBoolValue.True;
						}
					case PosFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.False;
							case PosFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case AnyFloat:
						return AbstractBoolValue.AnyBool;

					default:
						throw new Exception("Invalid AbstractValue type");
				}

			// equal
			case EQ:
				/**
				 * Neg == Neg -> Anybool
				 * Neg == Zero -> False
				 * Neg == Pos -> False
				 * Neg == Any -> Anybool
				 *
				 * Zero == Neg -> False
				 * Zero == Zero -> True
				 * Zero == Pos -> False
				 * Zero == Any -> Anybool
				 *
				 * Pos == Neg -> False
				 * Pos == Zero -> False
				 * Pos == Pos -> Anybool
				 * Pos == Any -> Anybool
				 *
				 * Any == Neg -> Anybool
				 * Any == Zero -> Anybool
				 * Any == Pos -> Anybool
				 * Any == Any -> Anybool
				 */

				switch (n1) {
					case NegInt:
						switch (n2) {
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.False;
							case NegInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case ZeroInt:
						switch (n2) {
							case NegInt:
							case PosInt:
								return AbstractBoolValue.False;
							case ZeroInt:
								return AbstractBoolValue.True;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case PosInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.False;
							case PosInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case AnyInt:
						return AbstractBoolValue.AnyBool;

					case NegFloat:
						switch (n2) {
							case NegFloat:
								return AbstractBoolValue.AnyBool;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.False;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case ZeroFloat:
						switch (n2) {
							case NegFloat:
							case PosFloat:
								return AbstractBoolValue.False;
							case ZeroFloat:
								return AbstractBoolValue.True;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case PosFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.False;
							case PosFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case AnyFloat:
						return AbstractBoolValue.AnyBool;

					default:
						throw new Exception("Invalid AbstractValue type");
				}
			// not equal
			case NE:
				/**
				 * Neg != Neg -> Anybool
				 * Neg != Zero -> True
				 * Neg != Pos -> True
				 * Neg != Any -> Anybool
				 *
				 * Zero != Neg -> True
				 * Zero != Zero -> False
				 * Zero != Pos -> True
				 * Zero != Any -> Anybool
				 *
				 * Pos != Neg -> True
				 * Pos != Zero -> True
				 * Pos != Pos -> Anybool
				 * Pos != Any -> Anybool
				 *
				 * Any != Neg -> Anybool
				 * Any != Zero -> Anybool
				 * Any != Pos -> Anybool
				 * Any != Any -> Anybool
				 */
				switch (n1) {
					case NegInt:
						switch (n2) {
							case NegInt:
								return AbstractBoolValue.AnyBool;
							case ZeroInt:
							case PosInt:
								return AbstractBoolValue.True;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case ZeroInt:
						switch (n2) {
							case NegInt:
							case PosInt:
								return AbstractBoolValue.True;
							case ZeroInt:
								return AbstractBoolValue.False;
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case PosInt:
						switch (n2) {
							case NegInt:
							case ZeroInt:
								return AbstractBoolValue.True;
							case PosInt:
							case AnyInt:
								return AbstractBoolValue.AnyBool;
						}
					case AnyInt:
						return AbstractBoolValue.AnyBool;

					case NegFloat:
						switch (n2) {
							case NegFloat:
								return AbstractBoolValue.AnyBool;
							case ZeroFloat:
							case PosFloat:
								return AbstractBoolValue.True;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case ZeroFloat:
						switch (n2) {
							case NegFloat:
							case PosFloat:
								return AbstractBoolValue.True;
							case ZeroFloat:
								return AbstractBoolValue.False;
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case PosFloat:
						switch (n2) {
							case NegFloat:
							case ZeroFloat:
								return AbstractBoolValue.True;
							case PosFloat:
							case AnyFloat:
								return AbstractBoolValue.AnyBool;
						}
					case AnyFloat:
						return AbstractBoolValue.AnyBool;
				}
		}
		// should never reach here, but for the sake of compiler
		return null;
	}
}
